<?php
/** 
 * A XML class
 * 
 * This class is used for storing structure and data of a XML document
 * 
 * @author Thuan Chau <thuanchau2020@gmail.com>
 * @package XMLNode
 */
class XMLNode {
	const NODE_NAME								= "name";
	const NODE_LEVEL							= "level";
	const NODE_PATH								= "path";
	const NODE_VALUE							= "value";
	const NODE_ATTRIBUTES						= "attributes";
	const NODE_CHILDS							= "childs";

	/**
	 * The name of the node
	 * 
	 * @access private
	 * @var string
	 */
	private $name;
	/**
	 * The level of the node
	 * 
	 * @access private
	 * @var int the level of root node is 1,
	 * and level of child node is equal to level of parent node + 1
	 */
	private $level;
	/**
	 * The value of the node
	 * 
	 * @access private
	 * @var string
	 */
	private $value;
	/**
	 * The position of the node
	 * 
	 * @access private
	 * @var int indicates the position of the node in array of child nodes, start is 1.
	 */
	private $position = 1;
	/**
	 * The attribute array of the node
	 * 
	 * @access private
	 * @var string[]
	 */
	private $attributes = array();
	/**
	 * The parent node of the node
	 * 
	 * @access private
	 * @var XMLNode
	 */
	private $parent = NULL;
	/**
	 * The child node array of the node
	 * 
	 * @access private
	 * @var XMLNode[]
	 */
	private $childs = array();

	/**
	 * The class constructor
	 * 
	 * @param int $level the level of the node
	 * @param string $name the name of the node
	 * @param string $value [optional] the value of the node
	 * @access public
	 */
	public function __construct($level, $name, $value = NULL) {
		$this->level = $level;
		$this->name = $name;
		$this->value = $value;
	}

	/**
	 * This get name for current node
	 * 
	 * @return string
	 * @access public
	 */
	public function getName() {
		return $this->name;
	}

	/**
	 * This set name for current node
	 * 
	 * @param string $name the new name
	 * @access public
	 */
	public function setName($name) {
		$this->name = $name;
	}

	/**
	 * This get level for current node
	 * 
	 * @return int
	 * @access public
	 */
	public function getLevel() {
		return $this->level;
	}

	/**
	 * This set level for current node
	 * 
	 * @param int $level the new level
	 * @access public
	 */
	public function setLevel($level) {
		$this->level = $level;
	}

	/**
	 * This get value for current node
	 * 
	 * @return string
	 * @access public
	 */
	public function getValue() {
		return $this->value;
	}
	
	/**
	 * This set value for current node
	 * 
	 * @param string $value the new value
	 * @access public
	 */
	public function setValue($value) {
		$this->value = $value;
	}

	/**
	 * This set an atrribute for current node
	 * 
	 * @param string $attName the attribute name
	 * @param string $attValue the attribute value
	 * @access public
	 */
	public function setAttribute($attName, $attValue) {
		$this->attributes[$attName] = $attValue;
	}

	/**
	 * This get value of an atttribute for current node
	 * 
	 * @param string $attName the attribute name
	 * @return string
	 * @access public
	 */
	public function getAttribute($attName) {
		return ( isset($this->attributes[$attName]) ? $this->attributes[$attName] : NULL );
	}

	/**
	 * This set atrributes for current node
	 * 
	 * @param string[] $attributes array of attributes
	 * @access public
	 */
	public function setAttributes($attributes) {
		$this->attributes = array();
		foreach ($attributes as $attName => $attValue) {
			$this->attributes[$attName] = $attValue;
		}
	}

	/**
	 * This get root node in XML structure
	 * 
	 * @return XMLNode root node
	 * @access public
	 */
	public function getRootNode() {
		$rootNode = $this;

		while (!is_null($rootNode->parent)) {
			$rootNode = $rootNode->parent;
		}

		return $rootNode;
	}

	/**
	 * This get parent node of current node
	 * 
	 * @return XMLNode parent node
	 * @access public
	 */
	public function getParentNode() {
		return $this->parent;
	}

	/**
	 * This get array of child nodes of current node
	 * 
	 * @return XMLNode[] child nodes array
	 * @access public
	 */
	public function getChildNodes() {
		return $this->childs;
	}

	/**
	 * This create a node and append it to childs of current node
	 * 
	 * @param string $name the name of the new node
	 * @param string $value [optional] the value of the new node
	 * @return XMLNode return the new node has been appended
	 * @access public
	 */
	public function appendChild($name, $value = NULL) {
		$childNode = new XMLNode($this->level + 1, $name, $value);
		
		$childNode->parent = $this;		
		$childNode->position =  count($this->childs) + 1;

		$this->childs[] = $childNode;

		return $childNode;
	}

	/**
	 * This get path of a node
	 * 
	 * @return string the path of the node. If root node, the path is 1,
	 * or else path of node is equal to "parent_path.node_position"
	 * @access public
	 * @example
	 * <p>
	 * <school><br/>
	 * <class name="A"><br/>
	 * <student name="student1" /><br/>
	 * </classA><br/>
	 * <class name="B"><br/>
	 * </class><br/>
	 * </school><br/>
	 * </p>
	 * then path of "student" node is "1.1.1", and path of "classB" is "1.2"
	 */
	public function getPath() {
		return ( !is_null($this->parent) ? $this->parent->getPath() . ".{$this->position}" : $this->position );
	}

	/**
	 * This get a node in XML structure by the path
	 * 
	 * @param string $path the path of the node
	 * @return XMLNode return the node if can find it, or else return NULL
	 * @access public
	 */
	public function getNodeByPath($path) {
		// split path to positions
		$positions = explode('.', $path);

		// return NULL if invalid path (valid: the root node's position must be 1)
		if ($positions[0] != 1) {
			return NULL;			
		}

		$node = $this->getRootNode();		
		for ($i = 1; $i < count($positions); $i++) {
			$position = $positions[$i];

			if (!empty($node->childs[$position - 1])) {
				$node = $node->childs[$position - 1];
			} else {
				return NULL;
			}
		}
		return $node;
	}

	/**
	 * Get array data of current node
	 * 
	 * @return mixed the XML structure and data of current node in array data
	 * @access private
	 */
	private function toArray() {
		$nodeData = array(
			self::NODE_NAME			=> $this->name,
			self::NODE_LEVEL		=> $this->level,
			self::NODE_PATH			=> $this->getPath(),
			self::NODE_VALUE		=> $this->value,
			self::NODE_ATTRIBUTES	=> $this->attributes,
			self::NODE_CHILDS		=> array()
		);
		
		foreach ($this->childs as $childNode) {
			$nodeData[self::NODE_CHILDS][] = $childNode->toArray();
		}

		return $nodeData;
	}

	/**
	 * Get json data of current node
	 * 
	 * @return mixed the XML structure and data of current node in json data
	 * @access public
	 */
	public function toJSON() {
		return json_encode($this->toArray());
	}
}
?>